﻿/*
ValidateModule attaches to "valueChanged" events from jeditable 
and validates the updated value with the provided rule set.

Depends:
	jquery.validate.js
	jquery.qtip.js

Author: James Kolpack
DateAuthored: 12/16/2009
*/

function ValidateModule() {
	var base = this;
	
	// rules for jquery.validate
	var rules;
	// messages for jquery.validate
	var messages;

	var qTipOptions;

	//Register for events we care about and store needed references.
	base.initialize = function(liteGrid, options) {
		if (!options.validateRules)
			throw ("ValidateModule must be initialized with options.validateRules");

		base.liteGrid = liteGrid;
		base.options = options;
		rules = options.validateRules;
		messages = options.validateMessages;
		qTipOptions = options.validateTooltipOptions ||
			{ // defaults
				position: { 
					corner: {
						target: 'topMiddle',
						tooltip: 'bottomMiddle'
					}
				},
				style: {
					background: '#F7BDBD',
					border: {
						radius: 3,
						color: '#DF3434',
					},
					tip: {
						corner: 'bottomMiddle',
						color: '#DF3434'
					}
				}
			};
		
		// register plugin for jeditable to bind the validation
		$.editable.types['text'].plugin = base.bindValidate;

		liteGrid.$el.bind("valueChanged", base.valueChanged);
		liteGrid.$el.bind("beforeSave", base.beforeSave);
		liteGrid.$el.bind("beforeDelete", base.beforeDelete);
		liteGrid.$el.bind("rowAdded", base.rowAdded);
		liteGrid.$el.bind("rowBound", base.rowBound);
	}

	base.bindValidate = function(settings, self) {
		var cell = settings.tdElement;

		// set up the options for jquery.validate
		var options = {
			rules: rules,
			messages: messages,
			errorPlacement: base.errorPlacement
		};

		$('form', self).validate(options);
	}
	
	base.errorPlacement = function errorPlacement(error, element) {
		var cell = $(element).parents('form').parent();
		if (cell.data('qtip') == undefined) {
			var options 
				= $.extend({content: { prerender: true, text: error.text() }}, 
						qTipOptions);
			cell.qtip(options);
			base.liteGrid.bodyDiv.bind('scroll', cell, base.scrollCell);
			cell.bind('focus', cell, base.focusCell);
		}
		cell.qtip("show");
	}

	base.scrollCell = function(event) {
		var cell = event.data;
		// HACK: the "base.liteGrid.bodyDiv.unbind('scroll', base.scrollCell);" is
		// working in most scenarios, but mysteriously doesn't unbind on occasion
		if (cell.data('qtip')) {
			cell.qtip("api").updatePosition();
			cell.qtip("hide");
		}
	}

	base.focusCell = function(event) {
	    var cell = event.data;
	    // HACK: check if qtip is defined on the object so that in the
	    //  case of nested forms, it won't explode
	    if (cell.data('qtip')) {
	        cell.qtip("show");
	    }
	}

	base.valueChanged = function(settings) {
		if (settings.tdElement == undefined)
			return;

		var cell = $(settings.tdElement);
		var row = cell.parents('tr:first');
		var dataItem = row.data("dataItem");
		var form = $('form', cell);
		
		if (form.valid()) {
			// it's valid, remove the elements for messages
		    cell.removeClass('invalid').removeClass('ui-state-error');
			if (cell.data('qtip')) {
			    // Note: some fixes applied from 
			    //  https://bugs.launchpad.net/qtip/bug/506473
			    //  https://bugs.launchpad.net/qtip/bug/506492 - delete comment on next rev of qtip
			    cell.qtip("destroy");
			    base.liteGrid.bodyDiv.unbind('scroll', base.scrollCell);
			    cell.unbind('focus', base.focusCell);
			}
			if ($('.invalid', row).length == 0)
				delete dataItem.invalid;
		}
		else {
			// it's invalid
			cell.addClass('invalid').addClass('ui-state-error');
			dataItem.invalid = true;
		}
	}

	base.beforeSave = function(settings) {
		$(settings.changes.dataItems).each(function(dataItem) {
			if (this.invalid) {
				settings.cancel = true;
				settings.cancelMessage
					= "There are one or more cells with invalid values preventing your action. "
					+ " Please correct the issues before trying again.";
			}
		});
	}
	
	base.beforeDelete = function(settings) {
		// TODO: at some point,
		// we'll want to allow deleting invalid and new rows, but for now,
		// it'll do more harm than good
		if (settings.dataItem.invalid) {
			settings.cancel = true;
			settings.cancelMessage 
				= "There are one or more cells with invalid values preventing deleting this row."
				+ " Please correct the issues before trying again.";
		}
	}

	base.rowAdded = function(settings) {
		initializeRow(settings.row);
	}

	base.rowBound = function(event, row, dataItem) {
		if (dataItem.IsNewRow)
			initializeRow(row);
	}

	function initializeRow(row) {
		var dataItem = row.data("dataItem");
		dataItem.invalid = true;

		$(base.options.columns).each(function(i) {
			var columnRules = rules[this.field];
			if (columnRules && columnRules.required) {
				// set the initial state of required cells
				// to invalid so that they have
				// to be populated before saving
				$('td:eq(' + i + ')', row).addClass('invalid').addClass('ui-state-error');
			}
		});
	}
}